using System;
using System.Collections;
using System.Collections.Specialized;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.ComponentModel;

using WCPierce.Web.UI;

[assembly: TagPrefix("WCPierce.Web.UI.WebControls", "wcp")]
namespace WCPierce.Web.UI.WebControls
{
    /// <summary>
    /// AutoCompleteTextBox is similar to the WinForm ComboBox control.  As the 
    /// user types into the box, the enter is "auto-completed" based on values 
    /// databound to the TextBox by the developer.
    /// </summary>
    [DefaultProperty("Text"), ToolboxData("<{0}:AutoCompleteTextBox runat=server></{0}:AutoCompleteTextBox>")]
    public class AutoCompleteTextBox : System.Web.UI.WebControls.TextBox
    {
        #region Member Variables

        /// <summary>
        /// The (relative) path to the AutoCompleteTextBox JavaScript file.
        /// </summary>
        private string _scriptPath = string.Empty;

        /// <summary>
        /// For using databinding with your AutoCompleteTextBox
        /// </summary>
        private object _dataSource = null;

        /// <summary>
        /// Data returned to the client is in the form of "entry"-newline-
        /// "entry"-newline...If you wanted to get cute, we could return in an XML
        ///  format.
        /// </summary>
        private static readonly string _FormatString = "{0}\n";

        /// <summary>
        /// If a ScriptPath isn't specified, check the web.config file for the 
        /// following key.
        /// </summary>
        private static readonly string _ScriptPath = "AutoCompleteTextBox.ScriptPath";

        /// <summary>
        /// CSS Class name for the list item of the dropdownlist.
        /// </summary>
        private string _listItemCssClass = string.Empty;

        /// <summary>
        /// CSS Class name for the "hover" effect of the list item of the dropdownlist.
        /// </summary>
        private string _listItemHoverCssClass = string.Empty;

        #endregion

        #region Public Properties

        /// <summary>
        /// The path to the AutoComplete.js file.  If you leave it blank, it will
        /// automatically look in the web.config for the value under the key
        /// "AutoCompleteTextBox.ScriptPath".  Should be a path relative to the 
        /// application root i.e. "~\scripts\AutoCompleteTextBox.js".
        /// </summary>
        public string ScriptPath
        {
            get
            {
                if (_scriptPath != string.Empty)
                    return ResolveUrl(_scriptPath);

                try
                {
                    return ResolveUrl(System.Configuration.ConfigurationSettings.AppSettings[AutoCompleteTextBox._ScriptPath]);
                }
                catch
                {
                    return null;
                }
            }
            set { _scriptPath = value; }
        }

        /// <summary>
        /// CSS Class name for the list item of the dropdownlist.
        /// </summary>
        public string ListItemCssClass
        {
            get { return _listItemCssClass; }
            set { _listItemCssClass = value; }
        }

        /// <summary>
        /// CSS Class name for the "hover" effect of the list item of the dropdownlist.
        /// </summary>
        public string ListItemHoverCssClass
        {
            get { return _listItemHoverCssClass; }
            set { _listItemHoverCssClass = value; }
        }

        /// <summary>
        /// For use with databinding.
        /// </summary>
        [Bindable(true), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), DefaultValue((string)null)]
        public virtual object DataSource
        {
            get
            {
                return _dataSource;
            }
            set
            {
                if (((value != null) && !(value is IListSource)) && !(value is IEnumerable))
                {
                    throw new ArgumentException("Invalid_DataSource_Type: " + this.ID);
                }
                _dataSource = value;
            }
        }

        /// <summary>
        /// For use with databinding.
        /// </summary>
        public virtual string DataTextField
        {
            get
            {
                object o = this.ViewState["DataTextField"];
                if (o != null)
                {
                    return (string)o;
                }
                return string.Empty;
            }
            set
            {
                this.ViewState["DataTextField"] = value;
            }
        }

        /// <summary>
        /// For use with databinding.
        /// </summary>
        public virtual string DataTextFormatString
        {
            get
            {
                object o = this.ViewState["DataTextFormatString"];
                if (o != null)
                {
                    return (string)o;
                }
                return string.Empty;
            }
            set
            {
                this.ViewState["DataTextFormatString"] = value;
            }
        }

        /// <summary>
        /// For use with databinding.
        /// </summary>
        [DefaultValue("")]
        public virtual string DataMember
        {
            get
            {
                object o = this.ViewState["DataMember"];
                if (o != null)
                {
                    return (string)o;
                }
                return string.Empty;
            }
            set
            {
                this.ViewState["DataMember"] = value;
            }
        }

        #endregion

        #region Overrides

        /// <summary>
        /// Render this control to the output parameter specified.
        /// </summary>
        /// <param name="output">The HTML writer to write out to.</param>
        protected override void Render(HtmlTextWriter output)
        {
            string uId = this.UniqueID;
            string newUid = uId.Replace(":", "_");
            string divId = newUid + "_Div";
            string jsId = newUid + "_Js";

            StringBuilder acScript = new StringBuilder();
            acScript.Append("<script type=\"text/javascript\">");
            acScript.AppendFormat("var {0} = new AutoCompleteTextBox('{1}','{2}');{0}.ListItemClass='{3}';{0}.ListItemHoverClass='{4}';", jsId, newUid, divId, this.ListItemCssClass, this.ListItemHoverCssClass);
            acScript.Append("</script>");

            this.Page.ClientScript.RegisterStartupScript(this.GetType(), newUid, acScript.ToString());

            base.Attributes.Add("AutoComplete", "False");
            base.Render(output);
            output.Write(String.Format("<div id='{0}'></div>", divId));
        }

        /// <summary>
        /// Register our common scripts and do default PreRendering.
        /// </summary>
        /// <param name="e"></param>
        protected override void OnPreRender(EventArgs e)
        {
            this._RegisterCommonScripts();
            base.OnPreRender(e);
        }

        /// <summary>
        /// Only fire the OnTextChanged event if this control is the target and it
        /// is a Call Back
        /// </summary>
        /// <param name="e"></param>
        protected override void OnTextChanged(EventArgs e)
        {
            if (Page.Request.Params["__EVENTTARGET"] == this.UniqueID && CallBackHelper.IsCallBack)
            {
                base.OnTextChanged(e);
            }
        }

        /// <summary>
        /// The original idea was to have the Auto Complete Text Box behave like a
        /// normal Data Bindable control.  But, alas, I couldn't figure that out.
        /// Thank you M$ for the databinding code.
        /// </summary>
        /// <param name="e"></param>
        protected override void OnDataBinding(EventArgs e)
        {
            base.OnDataBinding(e);

            IEnumerable ie = DataSourceHelper.GetResolvedDataSource(this.DataSource, this.DataMember);
            StringBuilder sb = new StringBuilder();

            if (ie != null)
            {
                bool useTextField = false;
                bool useFormatString = false;
                string textField = DataTextField;
                string formatString = DataTextFormatString;
                if (textField.Length != 0)
                {
                    useTextField = true;
                }
                if (formatString.Length != 0)
                {
                    useFormatString = true;
                }
                foreach (object o in ie)
                {
                    if (useTextField)
                    {
                        if (textField.Length > 0)
                        {
                            sb.AppendFormat(AutoCompleteTextBox._FormatString, DataBinder.GetPropertyValue(o, textField, formatString));
                        }
                    }
                    else
                    {
                        if (useFormatString)
                        {
                            sb.AppendFormat(AutoCompleteTextBox._FormatString, string.Format(formatString, o));
                        }
                        else
                        {
                            sb.AppendFormat(AutoCompleteTextBox._FormatString, o.ToString());
                        }
                    }  // useTextField
                }  // foreach
            } // ie != null

            // Remove trailing '\n'
            if (sb.Length > 1)
                sb.Remove(sb.Length - 1, 1);


            CallBackHelper.Write(sb.ToString());
        }


        /// <summary>
        /// Perhaps in the future, I will figure out how to make this work.  Before
        /// you email me with the answer please try using an AutoCompleteTextBox in
        /// a DataGrid with ViewState disabled.
        /// </summary>
        public override void DataBind()
        {
            // Do Nothing
        }

        /// <summary>
        /// What's the point if the developer turns on AutoPostBack?
        /// </summary>
        public override bool AutoPostBack
        {
            get { return false; }
        }


        #endregion

        #region Public Methods

        /// <summary>
        /// For now, Developer's must call this method to bind to their 
        /// Auto Complete Text Box.
        /// </summary>
        public virtual void BindData()
        {
            this.OnDataBinding(EventArgs.Empty);
        }

        #endregion

        #region Helper Methods

        /// <summary>
        /// Add a reference to the JavaScript, but only once per page.
        /// </summary>
        private void _RegisterCommonScripts()
        {
            if (!Page.ClientScript.IsClientScriptBlockRegistered("AutoCompleteTextBox"))
            {
                StringBuilder script = new StringBuilder();
                script.AppendFormat("<script type='text/javascript' src='{0}'></script>", this.ScriptPath);
                this.Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "AutoCompleteTextBox", script.ToString());
            }
        }

        #endregion
    }
}
